﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using l1cs.server.server.types;
using l1cs.server.datatables;
using l1cs.server.model.Instance;

namespace l1cs.server.model.map
{
    class L1V1Map : L1Map
    {
        private static Logger _log = Logger.getLogger( typeof( L1Map ).FullName );

        private int _mapId;

        private int _worldTopLeftX;

        private int _worldTopLeftY;

        private int _worldBottomRightX;

        private int _worldBottomRightY;

        private byte[,] _map;

        private bool _isUnderwater;

        private bool _isMarkable;

        private bool _isTeleportable;

        private bool _isEscapable;

        private bool _isUseResurrection;

        private bool _isUsePainwand;

        private bool _isEnabledDeathPenalty;

        private bool _isTakePets;

        private bool _isRecallPets;

        private bool _isUsableItem;

        private bool _isUsableSkill;

        /*
         * マップ情報を1面で保持するために仕方なくビットフラグ。 可読性が大きく下がるので良い子は真似しない。
         */
        /**
         * Mobなどの通行不可能になるオブジェクトがタイル上に存在するかを示すビットフラグ
         */
        private static byte BITFLAG_IS_IMPASSABLE = (byte)128; // 1000 0000

        protected L1V1Map()
        {

        }

        public L1V1Map( int mapId, byte[,] map, int worldTopLeftX,
                int worldTopLeftY, bool underwater, bool markable,
                bool teleportable, bool escapable, bool useResurrection,
                bool usePainwand, bool enabledDeathPenalty, bool takePets,
                bool recallPets, bool usableItem, bool usableSkill )
        {
            _mapId = mapId;
            _map = map;
            _worldTopLeftX = worldTopLeftX;
            _worldTopLeftY = worldTopLeftY;

            _worldBottomRightX = worldTopLeftX + map.GetUpperBound( 0 ) - 1;
            _worldBottomRightY = worldTopLeftY + map.GetUpperBound( 1 ) - 1;

            _isUnderwater = underwater;
            _isMarkable = markable;
            _isTeleportable = teleportable;
            _isEscapable = escapable;
            _isUseResurrection = useResurrection;
            _isUsePainwand = usePainwand;
            _isEnabledDeathPenalty = enabledDeathPenalty;
            _isTakePets = takePets;
            _isRecallPets = recallPets;
            _isUsableItem = usableItem;
            _isUsableSkill = usableSkill;
        }

        public L1V1Map( L1V1Map map )
        {
            _mapId = map._mapId;

            // _mapをコピー
            _map.CopyTo( map._map, 0 );

            _worldTopLeftX = map._worldTopLeftX;
            _worldTopLeftY = map._worldTopLeftY;
            _worldBottomRightX = map._worldBottomRightX;
            _worldBottomRightY = map._worldBottomRightY;

        }

        private int accessTile( int x, int y )
        {
            if ( !isInMap( x, y ) )
            { // XXX とりあえずチェックする。これは良くない。
                return 0;
            }

            return _map[x - _worldTopLeftX, y - _worldTopLeftY];
        }

        private int accessOriginalTile( int x, int y )
        {
            return accessTile( x, y ) & ( ~BITFLAG_IS_IMPASSABLE );
        }

        private void setTile( int x, int y, int tile )
        {
            if ( !isInMap( x, y ) )
            { // XXX とりあえずチェックする。これは良くない。
                return;
            }
            _map[x - _worldTopLeftX, y - _worldTopLeftY] = (byte)tile;
        }

        /*
         * ものすごく良くない気がする
         */
        public byte[,] getRawTiles()
        {
            return _map;
        }

        /// <summary>
        /// このマップのマップIDを返す。
        /// </summary>
        /// <returns>マップID</returns>
        override
        public int getId()
        {
            return _mapId;
        }

        override
        public int getX()
        {
            return _worldTopLeftX;
        }

        override
        public int getY()
        {
            return _worldTopLeftY;
        }

        override
        public int getWidth()
        {
            return _worldBottomRightX - _worldTopLeftX + 1;
        }

        override
        public int getHeight()
        {
            // Auto-generated method stub
            return _worldBottomRightY - _worldTopLeftY + 1;
        }

        override
        public int getTile( int x, int y )
        {
            short tile = _map[x - _worldTopLeftX, y - _worldTopLeftY];
            if ( 0 != ( tile & BITFLAG_IS_IMPASSABLE ) )
            {
                return 300;
            }
            return accessOriginalTile( x, y );
        }

        override
        public int getOriginalTile( int x, int y )
        {
            return accessOriginalTile( x, y );
        }

        override
        public bool isInMap( Point pt )
        {
            return isInMap( pt.getX(), pt.getY() );
        }

        override
        public bool isInMap( int x, int y )
        {
            // フィールドの茶色エリアの判定
            if ( _mapId == 4
                    && ( x < 32520 || y < 32070 || ( y < 32190 && x < 33950 ) ) )
            {
                return false;
            }
            return ( _worldTopLeftX <= x && x <= _worldBottomRightX
                    && _worldTopLeftY <= y && y <= _worldBottomRightY );
        }

        override
        public bool isPassable( Point pt )
        {
            return isPassable( pt.getX(), pt.getY() );
        }

        override
        public bool isPassable( int x, int y )
        {
            return isPassable( x, y - 1, 4 ) || isPassable( x + 1, y, 6 )
                    || isPassable( x, y + 1, 0 ) || isPassable( x - 1, y, 2 );
        }

        override
        public bool isPassable( Point pt, int heading )
        {
            return isPassable( pt.getX(), pt.getY(), heading );
        }

        override
        public bool isPassable( int x, int y, int heading )
        {
            // 現在のタイル
            int tile1 = accessTile( x, y );
            // 移動予定のタイル
            int tile2;

            if ( heading == 0 )
            {
                tile2 = accessTile( x, y - 1 );
            }
            else if ( heading == 1 )
            {
                tile2 = accessTile( x + 1, y - 1 );
            }
            else if ( heading == 2 )
            {
                tile2 = accessTile( x + 1, y );
            }
            else if ( heading == 3 )
            {
                tile2 = accessTile( x + 1, y + 1 );
            }
            else if ( heading == 4 )
            {
                tile2 = accessTile( x, y + 1 );
            }
            else if ( heading == 5 )
            {
                tile2 = accessTile( x - 1, y + 1 );
            }
            else if ( heading == 6 )
            {
                tile2 = accessTile( x - 1, y );
            }
            else if ( heading == 7 )
            {
                tile2 = accessTile( x - 1, y - 1 );
            }
            else
            {
                return false;
            }

            if ( ( tile2 & BITFLAG_IS_IMPASSABLE ) == BITFLAG_IS_IMPASSABLE )
            {
                return false;
            }

            if ( heading == 0 )
            {
                return ( tile1 & 0x02 ) == 0x02;
            }
            else if ( heading == 1 )
            {
                int tile3 = accessTile( x, y - 1 );
                int tile4 = accessTile( x + 1, y );
                return ( tile3 & 0x01 ) == 0x01 || ( tile4 & 0x02 ) == 0x02;
            }
            else if ( heading == 2 )
            {
                return ( tile1 & 0x01 ) == 0x01;
            }
            else if ( heading == 3 )
            {
                int tile3 = accessTile( x, y + 1 );
                return ( tile3 & 0x01 ) == 0x01;
            }
            else if ( heading == 4 )
            {
                return ( tile2 & 0x02 ) == 0x02;
            }
            else if ( heading == 5 )
            {
                return ( tile2 & 0x01 ) == 0x01 || ( tile2 & 0x02 ) == 0x02;
            }
            else if ( heading == 6 )
            {
                return ( tile2 & 0x01 ) == 0x01;
            }
            else if ( heading == 7 )
            {
                int tile3 = accessTile( x - 1, y );
                return ( tile3 & 0x02 ) == 0x02;
            }

            return false;
        }

        override
        public void setPassable( Point pt, bool isPassable )
        {
            setPassable( pt.getX(), pt.getY(), isPassable );
        }

        override
        public void setPassable( int x, int y, bool isPassable )
        {
            if ( isPassable )
            {
                setTile( x, y, (short)( accessTile( x, y ) & ( ~BITFLAG_IS_IMPASSABLE ) ) );
            }
            else
            {
                setTile( x, y, (short)( accessTile( x, y ) | BITFLAG_IS_IMPASSABLE ) );
            }
        }

        override
        public bool isSafetyZone( Point pt )
        {
            return isSafetyZone( pt.getX(), pt.getY() );
        }

        override
        public bool isSafetyZone( int x, int y )
        {
            int tile = accessOriginalTile( x, y );

            return ( tile & 0x30 ) == 0x10;
        }

        override
        public bool isCombatZone( Point pt )
        {
            return isCombatZone( pt.getX(), pt.getY() );
        }

        override
        public bool isCombatZone( int x, int y )
        {
            int tile = accessOriginalTile( x, y );

            return ( tile & 0x30 ) == 0x20;
        }

        override
        public bool isNormalZone( Point pt )
        {
            return isNormalZone( pt.getX(), pt.getY() );
        }

        override
        public bool isNormalZone( int x, int y )
        {
            int tile = accessOriginalTile( x, y );
            return ( tile & 0x30 ) == 0x00;
        }

        override
        public bool isArrowPassable( Point pt )
        {
            return isArrowPassable( pt.getX(), pt.getY() );
        }

        override
        public bool isArrowPassable( int x, int y )
        {
            return ( accessOriginalTile( x, y ) & 0x0e ) != 0;
        }

        override
        public bool isArrowPassable( Point pt, int heading )
        {
            return isArrowPassable( pt.getX(), pt.getY(), heading );
        }

        override
        public bool isArrowPassable( int x, int y, int heading )
        {
            // 現在のタイル
            int tile1 = accessTile( x, y );
            // 移動予定のタイル
            int tile2;
            // 移動予定の座標
            int newX;
            int newY;

            if ( heading == 0 )
            {
                tile2 = accessTile( x, y - 1 );
                newX = x;
                newY = y - 1;
            }
            else if ( heading == 1 )
            {
                tile2 = accessTile( x + 1, y - 1 );
                newX = x + 1;
                newY = y - 1;
            }
            else if ( heading == 2 )
            {
                tile2 = accessTile( x + 1, y );
                newX = x + 1;
                newY = y;
            }
            else if ( heading == 3 )
            {
                tile2 = accessTile( x + 1, y + 1 );
                newX = x + 1;
                newY = y + 1;
            }
            else if ( heading == 4 )
            {
                tile2 = accessTile( x, y + 1 );
                newX = x;
                newY = y + 1;
            }
            else if ( heading == 5 )
            {
                tile2 = accessTile( x - 1, y + 1 );
                newX = x - 1;
                newY = y + 1;
            }
            else if ( heading == 6 )
            {
                tile2 = accessTile( x - 1, y );
                newX = x - 1;
                newY = y;
            }
            else if ( heading == 7 )
            {
                tile2 = accessTile( x - 1, y - 1 );
                newX = x - 1;
                newY = y - 1;
            }
            else
            {
                return false;
            }

            if ( isExistDoor( newX, newY ) )
            {
                return false;
            }

            // if (Config.ARROW_PASS_FLOWER_BED) {
            // if (tile2 == 0x00 || (tile2 & 0x10) == 0x10) { // 花壇
            // if (tile2 == 0x00) { // 花壇
            // return true;
            // }
            // }

            if ( heading == 0 )
            {
                return ( tile1 & 0x08 ) == 0x08;
            }
            else if ( heading == 1 )
            {
                int tile3 = accessTile( x, y - 1 );
                int tile4 = accessTile( x + 1, y );
                return ( tile3 & 0x04 ) == 0x04 || ( tile4 & 0x08 ) == 0x08;
            }
            else if ( heading == 2 )
            {
                return ( tile1 & 0x04 ) == 0x04;
            }
            else if ( heading == 3 )
            {
                int tile3 = accessTile( x, y + 1 );
                return ( tile3 & 0x04 ) == 0x04;
            }
            else if ( heading == 4 )
            {
                return ( tile2 & 0x08 ) == 0x08;
            }
            else if ( heading == 5 )
            {
                return ( tile2 & 0x04 ) == 0x04 || ( tile2 & 0x08 ) == 0x08;
            }
            else if ( heading == 6 )
            {
                return ( tile2 & 0x04 ) == 0x04;
            }
            else if ( heading == 7 )
            {
                int tile3 = accessTile( x - 1, y );
                return ( tile3 & 0x08 ) == 0x08;
            }

            return false;
        }

        override
        public bool isUnderwater()
        {
            return _isUnderwater;
        }

        override
        public bool isMarkable()
        {
            return _isMarkable;
        }

        override
        public bool isTeleportable()
        {
            return _isTeleportable;
        }

        override
        public bool isEscapable()
        {
            return _isEscapable;
        }

        override
        public bool isUseResurrection()
        {
            return _isUseResurrection;
        }

        override
        public bool isUsePainwand()
        {
            return _isUsePainwand;
        }

        override
        public bool isEnabledDeathPenalty()
        {
            return _isEnabledDeathPenalty;
        }

        override
        public bool isTakePets()
        {
            return _isTakePets;
        }

        override
        public bool isRecallPets()
        {
            return _isRecallPets;
        }

        override
        public bool isUsableItem()
        {
            return _isUsableItem;
        }

        override
        public bool isUsableSkill()
        {
            return _isUsableSkill;
        }

        override
        public bool isFishingZone( int x, int y )
        {
            return accessOriginalTile( x, y ) == 16;
        }

        override
        public bool isExistDoor( int x, int y )
        {
            foreach ( L1DoorInstance door in DoorSpawnTable.getInstance().getDoorList() )
            {
                if ( _mapId != door.getMapId() )
                {
                    continue;
                }
                if ( door.getOpenStatus() == ActionCodes.ACTION_Open )
                {
                    continue;
                }
                if ( door.isDead() )
                {
                    continue;
                }
                int leftEdgeLocation = door.getLeftEdgeLocation();
                int rightEdgeLocation = door.getRightEdgeLocation();
                int size = rightEdgeLocation - leftEdgeLocation;
                if ( size == 0 )
                { // 1マス分の幅のドア
                    if ( x == door.getX() && y == door.getY() )
                    {
                        return true;
                    }
                }
                else
                { // 2マス分以上の幅があるドア
                    if ( door.getDirection() == 0 )
                    { // ／向き
                        for ( int doorX = leftEdgeLocation;
                                doorX <= rightEdgeLocation; doorX++ )
                        {
                            if ( x == doorX && y == door.getY() )
                            {
                                return true;
                            }
                        }
                    }
                    else
                    { // ＼向き
                        for ( int doorY = leftEdgeLocation;
                                doorY <= rightEdgeLocation; doorY++ )
                        {
                            if ( x == door.getX() && y == doorY )
                            {
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }

        override
        public String toString( Point pt )
        {
            return "" + getOriginalTile( pt.getX(), pt.getY() );
        }
    }

}
